using Org.BouncyCastle.Asn1.GM;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SM2Console
{
    public class Sm2Helper
    {
        // SM2国密标准曲线参数（固定为sm2p256v1）
        private static readonly ECDomainParameters ecDomainParameters = new ECDomainParameters(
            GMNamedCurves.GetByName("sm2p256v1").Curve,
            GMNamedCurves.GetByName("sm2p256v1").G,  // 生成元G
            GMNamedCurves.GetByName("sm2p256v1").N,  // 阶N
            GMNamedCurves.GetByName("sm2p256v1").H); // 余因子H

        /// <summary>
        /// SM2加密（公钥加密）
        /// </summary>
        /// <param name="plainText">明文（UTF-8字符串）</param>
        /// <param name="publicKeyHex">公钥（16进制字符串，128或130字符）</param>
        /// <returns>加密后的密文（16进制字符串）</returns>
        public static string Encrypt(string plainText, string publicKeyHex)
        {
            // 1. 校验输入
            if (string.IsNullOrWhiteSpace(plainText))
                throw new ArgumentException("明文不能为空", nameof(plainText));
            if (string.IsNullOrWhiteSpace(publicKeyHex))
                throw new ArgumentException("公钥不能为空", nameof(publicKeyHex));

            // 2. 处理公钥：移除可能的04前缀（未压缩格式标记）
            string processedPublicKey = publicKeyHex.Length == 130
                ? publicKeyHex.Substring(2)  // 去掉前2个字符（04）
                : publicKeyHex;

            // 3. 校验公钥长度（处理后必须为128字符，x和y各64字符）
            if (processedPublicKey.Length != 128)
                throw new ArgumentException("公钥格式错误，处理后长度应为128字符", nameof(publicKeyHex));

            // 4. 分割公钥的x、y坐标（16进制字符串→字节数组）
            byte[] xBytes = HexToBytes(processedPublicKey.Substring(0, 64));  // x坐标（前64字符）
            byte[] yBytes = HexToBytes(processedPublicKey.Substring(64, 64)); // y坐标（后64字符）

            // 5. 创建公钥参数（椭圆曲线点）
            ECPoint publicPoint = ecDomainParameters.Curve.CreatePoint(
                new BigInteger(1, xBytes),  // 正数解析x
                new BigInteger(1, yBytes)   // 正数解析y
            );
            ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicPoint, ecDomainParameters);

            // 6. 初始化SM2加密引擎（使用C1C3C2模式，与国密标准一致）
            SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
            // 加密模式需要随机数，使用BC的安全随机数生成器
            sm2Engine.Init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));

            // 7. 执行加密：明文→UTF8字节→加密字节→16进制密文
            byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
            byte[] cipherBytes = sm2Engine.ProcessBlock(plainBytes, 0, plainBytes.Length);
            return Remove04Prefix(BytesToHex(cipherBytes));
        }

        /// <summary>
        /// SM2解密（私钥解密）
        /// </summary>
        /// <param name="cipherTextHex">密文（16进制字符串）</param>
        /// <param name="privateKeyHex">私钥（16进制字符串，64字符）</param>
        /// <returns>解密后的明文（UTF-8字符串）</returns>
        public static string Decrypt(string cipherTextHex, string privateKeyHex)
        {
            // 1. 校验输入
            if (string.IsNullOrWhiteSpace(cipherTextHex))
                throw new ArgumentException("密文不能为空", nameof(cipherTextHex));
            if (string.IsNullOrWhiteSpace(privateKeyHex))
                throw new ArgumentException("私钥不能为空", nameof(privateKeyHex));
            if (privateKeyHex.Length != 64)
                throw new ArgumentException("私钥格式错误，长度应为64字符", nameof(privateKeyHex));

            // 2. 私钥转换：16进制字符串→字节数组→BigInteger
            byte[] privateKeyBytes = HexToBytes(privateKeyHex);
            BigInteger d = new BigInteger(1, privateKeyBytes);  // 正数解析私钥

            // 3. 创建私钥参数
            ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(d, ecDomainParameters);

            // 4. 初始化解密引擎（C1C3C2模式）
            SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
            sm2Engine.Init(false, privateKeyParams);  // false表示解密模式

            // 5. 执行解密：密文16进制→字节数组→解密字节→UTF8明文
            byte[] cipherBytes = HexToBytes(Add04PrefixIfNeeded(cipherTextHex));
            byte[] plainBytes = sm2Engine.ProcessBlock(cipherBytes, 0, cipherBytes.Length);
            return Encoding.UTF8.GetString(plainBytes);
        }

        /// <summary>
        /// 16进制字符串转字节数组（辅助方法）
        /// </summary>
        private static byte[] HexToBytes(string hex)
        {
            if (hex.Length % 2 != 0)
                throw new FormatException("16进制字符串长度必须为偶数");

            byte[] bytes = new byte[hex.Length / 2];
            for (int i = 0; i < hex.Length; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            }
            return bytes;
        }

        /// <summary>
        /// 字节数组转16进制字符串（辅助方法）
        /// </summary>
        private static string BytesToHex(byte[] bytes)
        {
            StringBuilder sb = new StringBuilder(bytes.Length * 2);
            foreach (byte b in bytes)
            {
                sb.AppendFormat("{0:X2}", b);  // 大写16进制，与多数系统兼容
            }
            return sb.ToString();
        }
        
        private static string Remove04Prefix(String hex) {
            return hex.StartsWith("04") ? hex.Substring(2) : hex;
        }
        
        private static string Add04PrefixIfNeeded(String hex) {
            return hex.StartsWith("04") ? hex : "04" + hex;
        }
    }
}